/*   Copyright (C) 2008  Jeroen v. Schagen & Youri Lima
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;

namespace Engine.Math
{
    /// <summary>
    ///     Represents an orientation in a three-dimensional space.
    /// </summary>
    public struct Quaternion
    {
        public float w;
        public float x;
        public float y;
        public float z;

        # region Constant

        /// <summary>
        ///     The representation of an empty quaternion.
        /// </summary>
        public static Quaternion ZERO
        {
            get { return new Quaternion(0, 0, 0, 0); }
        }

        /// <summary>
        ///     The representation of an identity quaternion.
        /// 
        ///     In an identity quaternion, the w is valued at one.
        ///     All other values are set at zero.
        /// </summary>
        public static Quaternion IDENTITY
        {
            get { return new Quaternion(1, 0, 0, 0); }
        }

        # endregion


        # region Constructor

        /// <summary>
        ///     Construct quaternion using float values.
        /// </summary>
        /// 
        /// <param name="w">W</param>
        /// <param name="x">X</param>
        /// <param name="y">Y</param>
        /// <param name="z">Z</param>
        public Quaternion(float w, float x, float y, float z)
        {
            this.w = w;
            this.x = x;
            this.y = y;
            this.z = z;
        }

        /// <summary>
        ///     Constructor quaternion using a quaterion object.
        /// </summary>
        /// 
        /// <param name="q">Quaternion</param>
        public Quaternion(Quaternion q)
        {
            this.w = q.w;
            this.x = q.x;
            this.y = q.y;
            this.z = q.z;
        }

        # endregion


        # region Function

        /// <summary>
        ///     Compares this object to parametered object.
        /// </summary>
        /// 
        /// <param name="obj"></param>
        /// <returns></returns>
        public override bool Equals(object obj)
        {
            return base.Equals(obj);
        }

        /// <summary>
        ///     Create a quaternion, using an angle (degree) and an axis.
        /// </summary>
        /// 
        /// <param name="angle">Angle</param>
        /// <param name="axis">Axis</param>
        /// <returns>Quaternion</returns>
        public static Quaternion FromAngleAxis(Degree angle, Vector3 axis)
        {
            return FromAngleAxis(new Radian(angle), axis);
        }

        /// <summary>
        ///     Create a quaternion, using an angle (radian) and an axis.
        /// </summary>
        /// 
        /// <param name="angle">Angle</param>
        /// <param name="axis">Axis</param>
        /// <returns>Quaternion</returns>
        public static Quaternion FromAngleAxis(Radian angle, Vector3 axis)
        {
            float cos = (float)System.Math.Cos(angle.value / 2);
            float sin = (float)System.Math.Sin(angle.value / 2);

            return new Quaternion(cos, axis.x * sin, axis.y * sin, axis.z * sin);
        }

        /// <summary>
        ///     Retrieves the hashcode of current object.
        /// </summary>
        /// 
        /// <returns></returns>
        public override int GetHashCode()
        {
            return base.GetHashCode();
        }

        /// <summary>
        ///     Inverse the orientation of this quaternion.
        /// </summary>
        /// 
        /// <returns></returns>
        public Quaternion Inverse()
        {
            // Retrieve length
            float length = this.Length;

            if (length > 0.0f)
            {
                // Calculate inverse norm
                float invNorm = 1.0f / length;

                // Return quaternion
                return new Quaternion(this.w * invNorm, -this.x * invNorm, -this.y * invNorm, -this.z * invNorm);
            }

            // Return zero
            return ZERO;
        }

        /// <summary>
        ///     Calculate the length of this quaternion.
        /// </summary>
        /// 
        /// <returns></returns>
        public float Length
        {
            get { return w * w + x * x + y * y + z * z; }
        }

        /// <summary>
        ///     Normalise this quaternion.
        /// </summary>
        /// 
        /// <returns></returns>
        public float Normalise()
        {
            float length = this.Length;

            // Multiply class by factor
            float factor = 1.0f / (float) System.Math.Sqrt((double) length);

            this.w = this.w * factor;
            this.x = this.x * factor;
            this.y = this.y * factor;
            this.z = this.z * factor;

            // Return length
            return length;
        }

        /*
        public Vector3 Unit
        {
            get
            {
                float scale = (float) System.Math.Sqrt((double) this.Length);

                float x = this.x / scale;
                float y = this.y / scale;
                float z = this.z / scale;

                return new Vector3(x, y, z);
            }
        }
        */

        # endregion


        # region Operator

        // Arithmetic
        public static Quaternion operator +(Quaternion q1, Quaternion q2)
        {
            return new Quaternion( q1.w + q2.w,
                             q1.x + q2.x,
                             q1.y + q2.y,
                             q1.z + q2.z );
        }

        public static Quaternion operator -(Quaternion q1, Quaternion q2)
        {
            return new Quaternion( q1.w - q2.w,
                             q1.x - q2.x,
                             q1.y - q2.y,
                             q1.z - q2.z );
        }

        public static Quaternion operator *(Quaternion q1, Quaternion q2)
        {
            return new Quaternion( q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z,
                             q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y,
                             q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z,
                             q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x );
        }

        // Comparison
        public static bool operator ==(Quaternion q1, Quaternion q2)
        {
            return (q1.w == q2.w) && (q1.x == q2.x) && (q1.y == q2.y) && (q1.z == q2.z);
        }

        public static bool operator !=(Quaternion q1, Quaternion q2)
        {
            return (q1.w != q2.w) || (q1.x != q2.x) || (q1.y != q2.y) || (q1.z != q2.z);
        }

        public static bool operator >(Quaternion q1, Quaternion q2)
        {
            return q1.Length > q2.Length;
        }

        public static bool operator <(Quaternion q1, Quaternion q2)
        {
            return q1.Length < q2.Length;
        }

        public static bool operator >=(Quaternion q1, Quaternion q2)
        {
            return q1.Length >= q2.Length;
        }

        public static bool operator <=(Quaternion q1, Quaternion q2)
        {
            return q1.Length <= q2.Length;
        }

        # endregion


        # region Cross-class operator

        // Float
        public static Quaternion operator +(Quaternion q, float value)
        {
            return new Quaternion( q.w + value,
                             q.x + value,
                             q.y + value,
                             q.z + value );
        }

        public static Quaternion operator -(Quaternion q, float value)
        {
            return new Quaternion( q.w - value,
                             q.x - value,
                             q.y - value,
                             q.z - value );
        }

        public static Quaternion operator *(Quaternion q, float value)
        {
            return new Quaternion( q.w * value,
                             q.x * value,
                             q.y * value,
                             q.z * value );
        }

        public static Quaternion operator /(Quaternion q, float value)
        {
            return new Quaternion( q.w / value,
                             q.x / value,
                             q.y / value,
                             q.z / value );
        }

        // Vector3
        public static Vector3 operator *(Quaternion q, Vector3 v)
        {
            // Initiate vectors
            Vector3 qvector = new Vector3(q.x, q.y, q.z);
            Vector3 tvector1 = Vector3.ZERO;
            Vector3 tvector2 = Vector3.ZERO;

            // Cross product
            tvector1 = qvector.CrossProduct(v);
            tvector2 = qvector.CrossProduct(tvector1);

            // Apply scalar
            tvector1 = tvector1 * (q.w * 2.0f);
            tvector2 = tvector2 * 2.0f;

            // Retrieve sum
            return v + tvector1 + tvector2;
        }

        # endregion

        public override string ToString()
        {
            return w.ToString() + ", " + x.ToString() + ", " + y.ToString() + ", " + z.ToString();
        }
    }
}